package com.dge.common.utils

import android.app.Activity
import android.text.TextUtils
import androidx.fragment.app.Fragment
import com.dge.common.common.AppConfig
import com.dge.common.extentions.isNotValid
import com.dge.common.extentions.isValid
import com.dge.common.interfaces.DownloadFileCallback
import com.dge.common.interfaces.OkHttpCallback
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import com.hjq.toast.ToastUtils
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.Proxy
import java.util.concurrent.TimeUnit

object OkHttpUtils {
    val TAG = "OkHttpUtils"
    val client = OkHttpClient()
    val MediaTypeJSON = "application/json; charset=utf-8".toMediaTypeOrNull()!!
    val MediaTypeDEFAULT = "application/x-www-form-urlencoded".toMediaTypeOrNull()!!
    val commonHeader = LinkedHashMap<String, Any>()
    val commonBody = LinkedHashMap<String, Any>()

    fun addCommonHeader(key: String, value: Any): OkHttpUtils {
        commonHeader[key] = value
        return this
    }

    fun addCommonBody(key: String, value: Any): OkHttpUtils {
        commonBody[key] = value
        return this
    }

    fun getCode(result: String): Int {
        var code = -1
        try {
//        Timber.d( "url = $url :-->$result")
            if (TextUtils.isEmpty(result)) {
                if (AppConfig.isDebug()) Timber.d("result is empty return")
                return -1;
            }
            val obj = JSONObject(result)
            val codeStr = obj.optString("code")
            if (codeStr.isValid() && TextUtils.isDigitsOnly(codeStr)) { //适配 code:200代表成功的情况
                code = codeStr.toInt()
            }
            if (obj.optBoolean("success")) { //适配 "success":true 代表成功的情况
                code = 200
            }
            if ("1" == obj.optString("success")) { //适配"success":"1"代表成功的情况
                code = 200
            }
            if ("ok" == obj.optString("stat")) { //适配"stat":"ok"代表成功的情况
                code = 200
            }
            if ("success" == obj.optString("type")) { //适配"type":"success"代表成功的情况
                code = 200
            }
            if ("1" == obj.optString("resultCode")) { //适配"resultCode":"1"代表成功的情况
                code = 200
            }
        } catch (e: java.lang.Exception) {
            if (AppConfig.isDebug()) Timber.d("getCode Exception  e = ${e.message}")
        }
        return code
    }

    /**
     * activity或者fragment被销毁了，此时不应该再走回调
     */
    private fun isFinished(tag: Any?): Boolean {
        return tag != null && ((tag is Activity && (tag.isDestroyed || tag.isFinishing))
                || (tag is Fragment && !tag.isAdded))
    }

    @Throws(IOException::class)
    fun <T> handleResult(
        url: String,
        response: Response,
        clazz: Class<T>,
        callback: OkHttpCallback<T>?,
        avoidNullValue: Boolean = false,
        tag: Any? = null
    ) {
        try {
            val result = response.body!!.string()
            if (result.isNotValid()) {
                if (isFinished(tag)) return
                MainScope().launch {
                    if (isFinished(tag)) return@launch
                    if (AppConfig.isDebug()) Timber.i("返回结果为空, url = $url")
                    callback?.onError(401, "返回结果为空")
                }
                return
            }
            val code = getCode(result)
            val obj = JSONObject(result)
            if (code == 200 || code == 0) { //200 或 0代表成功
                val any = if (avoidNullValue) {
                    val jsonObject = JsonParser.parseString(result).asJsonObject
                    GsonUtils.removeNullsFromJson(jsonObject)
                    GsonUtils.gson.fromJson(jsonObject, clazz)
                } else {
                    GsonUtils.gson.fromJson(result, clazz)
                }
                if (isFinished(tag)) return
                MainScope().launch {
                    if (isFinished(tag)) return@launch
                    callback?.onSuccess(any) //callback不为空的时候调用onSuccess
                }
            } else {
                var msg = obj.optString("message")
                if (msg.isNotValid()) msg = obj.optString("msg")
                if (msg.isNotValid()) msg = obj.optString("resultMsg") //畅聊模块接口返回这个
                if (isFinished(tag)) return
                MainScope().launch {
                    if (isFinished(tag)) return@launch
                    callback?.onError(code, msg)
                }
            }
        } catch (e: JSONException) {
            if (AppConfig.isDebug()) Timber.d("onFailure = ${e.message}， url = $url")
            if (isFinished(tag)) return
            MainScope().launch {
                if (isFinished(tag)) return@launch
                if (e.message == "timeout") {
                    callback?.onError(401, "请求超时")
                } else {
                    if (AppConfig.isDebug()) Timber.i("onFailure url = $url")
                    callback?.onFailure(e)
                }
            }
        } finally {
            response.close()
        }
    }

    inline fun <reified T> post(
        url: String,
        map: HashMap<String, Any>?,
        callback: OkHttpCallback<T>?,
    ) {
        post(url, null, null, map, callback)
    }

    inline fun <reified T> post(
        url: String,
        tag: Any?,
        map: HashMap<String, Any>?,
        callback: OkHttpCallback<T>?,
    ) {
        post(url, tag, null, map, callback)
    }

    inline fun <reified T> post(
        url: String,
        header: HashMap<String, Any>?,
        map: HashMap<String, Any>?,
        callback: OkHttpCallback<T>?,
    ) {
        post(url, null, header, map, callback)
    }

    inline fun <reified T> post(
        url: String,
        tag: Any?,
        header: HashMap<String, Any>?,
        body: HashMap<String, Any>?,
        callback: OkHttpCallback<T>?,
        type: MediaType = MediaTypeJSON,
    ) {
        post(url).withTag(tag).addHeaderAll(header).addBodyAll(body).withMediaType(type)
            .build(callback)
    }

    fun post(
        url: String,
    ): RequestParams {
        return RequestParams(url, "POST")
    }

    fun get(
        url: String,
    ): RequestParams {
        return RequestParams(url, "GET")
    }

    fun delete(
        url: String,
    ): RequestParams {
        return RequestParams(url, "DELETE")
    }

    fun put(
        url: String,
    ): RequestParams {
        return RequestParams(url, "PUT")
    }

    /**
     * @网络请求--get请求
     */
    inline operator fun <reified T> get(
        url: String,
        body: HashMap<String, Any>?,
        callback: OkHttpCallback<T>
    ) {
        get(url, null, null, body, callback)
    }

    /**
     * @网络请求--get请求
     */
    inline operator fun <reified T> get(
        url: String,
        tag: Any?,
        body: HashMap<String, Any>?,
        callback: OkHttpCallback<T>
    ) {
        get(url, tag, null, body, callback)
    }

    /**
     * @网络请求--get请求
     */
    inline fun <reified T> get(
        url: String,
        tag: Any?,
        header: HashMap<String, Any>?,
        body: HashMap<String, Any>?,
        callback: OkHttpCallback<T>?,
    ) {
        get(url).withTag(tag).addHeaderAll(header).addBodyAll(body).build(callback)
    }

    /**
     * @网络请求--post请求
     */
    inline fun <reified T> uploadFile(
        url: String,
        file: File,
        header: HashMap<String, Any>?,
        map: HashMap<String, Any>?,
        fileName: String? = null,
        avoidNullValue: Boolean = false,
        callback: OkHttpCallback<T>?
    ) {
        val requestBuilder = Request.Builder()
        for ((key, value) in commonHeader) {
            requestBuilder.addHeader(key, value.toString())
        }
        if (header != null)
            for ((key, value) in header) {
                requestBuilder.addHeader(key, value.toString())
            }
        val builder = client.newBuilder() //OkHttpClient.Builder()
        // 上传文件不打log
        // if (AppConfig.isDebug()) {
        //     builder.addNetworkInterceptor(HttpLogInterceptor())
        // }
        builder.proxy(if (AppConfig.allowNetProxy) null else Proxy.NO_PROXY)
            .writeTimeout(3, TimeUnit.MINUTES)
            .readTimeout(3, TimeUnit.MINUTES)
        val okHttpClient = builder.build()
        var temFileName = ""
        if (fileName == null) {
            if (TextUtils.isEmpty(file.name)) {
                ToastUtils.show("找不到该文件!")
            } else {
                temFileName = file.name
            }
        } else {
            temFileName = fileName
        }

        if (AppConfig.isDebug()) Timber.d("upload uploadFile fileName = $temFileName")
        val requestBody = MultipartBody.Builder()
        if (map != null) {
            for ((key, value) in map) {
                requestBody.addFormDataPart(key, value.toString())
            }
        }
        val formBody =
            requestBody.setType(MultipartBody.FORM)
                .addFormDataPart(
                    "file",
                    temFileName,
                    RequestBody.create("*/*".toMediaTypeOrNull(), file)
                ).build()
        val request = requestBuilder.post(formBody).url(url).build()
        okHttpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                if (call.isCanceled()) return
                if (AppConfig.isDebug()) Timber.i("onFailure: ${e.message}")
                MainScope().launch {
                    if (e.message == "timeout") {
                        callback?.onError(401, "请求超时")
                    } else {
                        callback?.onFailure(e)
                    }
                }
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call, response: Response) {
                if (call.isCanceled()) {
                    response.close()
                    return
                }
                val result = response.body!!.string()
                try {
                    val obj = JSONObject(result)
                    val code = obj.getInt("code")
                    if (code == 0 || code == 200) {
                        if (avoidNullValue) {
                            val jsonObject = JsonParser.parseString(result).asJsonObject
                            GsonUtils.removeNullsFromJson(jsonObject)
                            val bean = GsonUtils.gson.fromJson(jsonObject, T::class.java)
                            MainScope().launch {
                                callback?.onSuccess(bean)
                            }
                        } else {
                            val bean = GsonUtils.gson.fromJson(result, T::class.java)
                            MainScope().launch {
                                callback?.onSuccess(bean)
                            }
                        }
                    } else {
                        val msg = obj.optString("msg")
                        MainScope().launch {
                            callback?.onError(code, if (msg.isNotValid()) "未知错误，请联系管理员" else msg)
                            ToastUtils.show(msg)
                        }
                    }
                } catch (e: JSONException) {
                    if (AppConfig.isDebug()) Timber.d("JSONException e =  ${e.message}")
                    e.printStackTrace()
                } finally {
                    response.close()
                }
            }
        })
    }

    /**
     * 下载文件
     * @param url 文件url
     */
    fun downLoadFile(
        url: String,
        destFile: File,
        isReplace: Boolean,
        tag: Any? = null,
        callBack: DownloadFileCallback?
    ) {
        if (destFile.exists() && !isReplace) {
            callBack?.successCallBack(destFile)
            return
        }
        if (isReplace && destFile.exists()) {
            destFile.delete()
            try {
                destFile.createNewFile()
            } catch (e: Exception) { //设备没有空间的时候会抛出异常
                callBack?.failedCallBack(e.message ?: "创建文件时异常")
                ToastUtils.show(e.message)
                return
            }
        }
        if (!destFile.exists()) {
            try {
                destFile.parentFile?.mkdirs()
                destFile.createNewFile()
            } catch (e: Exception) { //设备没有空间的时候会抛出异常
                ToastUtils.show(e.message)
                callBack?.failedCallBack(e.message ?: "创建文件时异常")
                return
            }
        }
        val builder = Request.Builder()
        val request: Request = builder.get().url(url).tag(tag).build()
        val okHttpClient = client.newBuilder() //OkHttpClient.Builder()
            .proxy(if (AppConfig.allowNetProxy) null else Proxy.NO_PROXY)
//            .addNetworkInterceptor(HttpLogInterceptor())
            .writeTimeout(5, TimeUnit.MINUTES)
            .readTimeout(5, TimeUnit.MINUTES)
            .build()
        val call: Call = okHttpClient.newCall(request)
        call.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                if (call.isCanceled()) return
                if (AppConfig.isDebug()) Timber.e(e.toString())
                MainScope().launch { callBack?.failedCallBack("下载失败：${e.message}") }
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call, response: Response) {
                if (call.isCanceled()) {
                    response.close()
                    return
                }
                var inputStream: InputStream? = null
                val buf = ByteArray(2048)
                var len: Int
                var fos: FileOutputStream? = null
                try {
                    val total = response.body!!.contentLength()
                    if (AppConfig.isDebug()) Timber.e("total------>$total")
                    var current: Long = 0
                    inputStream = response.body!!.byteStream()
                    fos = FileOutputStream(destFile)
                    while (inputStream.read(buf).also { len = it } != -1) {
                        current += len.toLong()
                        fos.write(buf, 0, len)
                        MainScope().launch {
                            callBack?.onProgress(
                                total,
                                current
                            )
                        }
                    }
                    fos.flush()
                    MainScope().launch { callBack?.successCallBack(destFile) }
                } catch (e: IOException) {
                    if (AppConfig.isDebug()) Timber.e(e.toString())
                    MainScope().launch { callBack?.failedCallBack("下载失败：${e.message}") }
                } finally {
                    try {
                        inputStream?.close()
                        fos?.close()
                    } catch (e: IOException) {
                        if (AppConfig.isDebug()) Timber.e(e.toString())
                    }
                    response.close()
                }
            }
        })
    }

    /** 根据Tag取消请求  */
    fun cancelTag(tag: Any?) {
        if (tag == null) return
        for (call in client.dispatcher.queuedCalls()) {
            if (tag == call.request().tag()) {
                call.cancel()
            }
        }
        for (call in client.dispatcher.runningCalls()) {
            if (tag == call.request().tag()) {
                call.cancel()
            }
        }
    }

    /** 取消所有请求请求  */
    fun cancelAll() {
        for (call in client.dispatcher.queuedCalls()) {
            call.cancel()
        }
        for (call in client.dispatcher.runningCalls()) {
            call.cancel()
        }
    }

    class RequestParams(val url: String, private val requestType: String) {
        val header = HashMap<String, Any>()
        val body = HashMap<String, Any>()
        var tag: Any? = null
        var type: MediaType = MediaTypeDEFAULT
        var customReadTimeout: Long = -1
        var customWriteTimeout: Long = -1
        private var jsonBody = ""
        private var avoidNullValue = false
        fun addHeader(key: String, value: Any): RequestParams {
            header[key] = value
            return this
        }

        fun addHeaderAll(header: HashMap<String, Any>?): RequestParams {
            if (header != null)
                this.header.putAll(header)
            return this
        }

        /**
         * 设置读取超时时间
         */
        fun setReadTimeout(timeMills: Long): RequestParams {
            customReadTimeout = timeMills
            return this
        }

        /**
         * 设置写入超时时间
         */
        fun setWriteTimeout(timeMills: Long): RequestParams {
            customWriteTimeout = timeMills
            return this
        }

        /**
         * json 格式的body
         * medieType必须为json时才能使用
         * 不能和addBody及addBodyAll混用
         * 不可用于Get方法
         */
        fun addJsonBody(jsonString: String): RequestParams = apply {
            jsonBody = jsonString
        }

        fun addBody(jsonString: String): RequestParams {
            if (jsonString.isValid()) {
                try {
                    val mapObj = GsonUtils.gson.fromJson<Map<String?, Any?>?>(
                        jsonString,
                        object : TypeToken<Map<String?, Any?>?>() {}.type
                    )
                    if (mapObj != null && mapObj.isNotEmpty()) {
                        for (entry in mapObj) {
                            if (entry.key != null && entry.value != null) {
                                body[entry.key.toString()] = entry.value!!
                            }
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
            return this
        }

        fun addBody(key: String, value: Any): RequestParams {
            body[key] = value
            return this
        }

        fun addBodyAll(body: HashMap<String, Any>?): RequestParams {
            if (body != null)
                this.body.putAll(body)
            return this
        }

        fun jsonConfig(tag: Any, avoidNullValue: Boolean = true): RequestParams = apply {
            this.avoidNullValue = avoidNullValue
            type = MediaTypeJSON
            this.tag = tag
        }

        fun defaultConfig(tag: Any, avoidNullValue: Boolean = true): RequestParams = apply {
            this.avoidNullValue = avoidNullValue
            type = MediaTypeDEFAULT
            this.tag = tag
        }

        fun removeHeader(key: String): RequestParams = apply {
            header.remove(key)
        }

        fun removeBody(key: String): RequestParams = apply {
            body.remove(key)
        }

        /**
         * 填充json中为null的字段为data class中设置的默认值，根据业务判断是否要使用
         * 注意：如果一个数组中的某个数据为null值，则会删除改null值，并且数组size会减一
         */
        fun avoidNullValue(): RequestParams = apply {
            avoidNullValue = true
        }

        fun withTag(tag: Any?): RequestParams {
            this.tag = tag
            return this
        }

        fun withMediaType(type: MediaType): RequestParams {
            this.type = type
            return this
        }

        private fun checkJsonBody() {
            if (jsonBody.isValid()) {
                if (requestType == "GET")
                    throw IllegalArgumentException("addJsonBody不可用于GET方法！")
                if (type != MediaTypeJSON)
                    throw IllegalArgumentException("addJsonBody必须配合MediaTypeJSON使用！")
                if (body.size > 0)
                    throw IllegalArgumentException("使用addJsonBody不能和addBody方法一起使用！")
            }
        }

        /**
         * 同步返回Response，可配合协程使用
         */
        @Throws(Exception::class)
        fun execute(): Response {
            return execute(followRedirects = false, addLogInterceptor = true)
        }

        /**
         * 同步返回Response，可配合协程使用
         */
        @Throws(Exception::class)
        fun execute(followRedirects: Boolean = true, addLogInterceptor: Boolean = false): Response {
            val builder = Request.Builder()
            builder.url(url)
            if (AppConfig.isDebug()) Timber.d("build requestType = $requestType")
            return when (requestType) {
                "POST" -> {
                    val call = getPostCall(builder, followRedirects, addLogInterceptor)
                    call.execute()
                }

                "GET" -> {
                    val call = getGetCall(builder, followRedirects, addLogInterceptor)
                    call.execute()
                }

                "DELETE" -> {
                    val call = getDeleteCall(builder, followRedirects, addLogInterceptor)
                    call.execute()
                }

                "PUT" -> {
                    val call = getPutCall(builder, followRedirects, addLogInterceptor)
                    call.execute()
                }

                else -> {
                    val call = getPostCall(builder, followRedirects, addLogInterceptor)
                    call.execute()
                }
            }
        }

        /**
         * 同步返回，可和协程一起使用
         * 示例：
         * launch {
         *      val sync = async(Dispatchers.IO) {
         *         OkHttpUtils.get(URLConstants.YIKUIABAO)
         *         .addHeader("Authorization", UserInfoMgr.token)
         *         .execute(EasyExpressBean::class.java)
         *      }
         *      val bean = sync.await()
         *      Timber.d( "msg = ${bean?.data?.value?.message}")
         *  }
         */
        @Throws(Exception::class)
        fun <T> execute(clazz: Class<T>): T? {
            try {
                val builder = Request.Builder()
                builder.url(url)
                if (AppConfig.isDebug()) Timber.d("build requestType = $requestType")
                val response = when (requestType) {
                    "POST" -> {
                        val call = getPostCall(builder)
                        call.execute()
                    }

                    "GET" -> {
                        val call = getGetCall(builder)
                        call.execute()
                    }

                    "DELETE" -> {
                        val call = getDeleteCall(builder)
                        call.execute()
                    }

                    "PUT" -> {
                        val call = getPutCall(builder)
                        call.execute()
                    }

                    else -> {
                        val call = getPostCall(builder)
                        call.execute()
                    }
                }
                val result = response.body!!.string()
                val code = getCode(result)
                if (code == 200 || code == 0) { //200 或 0代表成功
                    if (avoidNullValue) {
                        val jsonObject = JsonParser.parseString(result).asJsonObject
                        GsonUtils.removeNullsFromJson(jsonObject)
                        return GsonUtils.gson.fromJson(jsonObject, clazz)
                    }
                    return GsonUtils.gson.fromJson(result, clazz)
                }
            } catch (e: Exception) {
                e.printStackTrace()
                if (AppConfig.isDebug()) Timber.d("execute e = ${e.message}")
            }
            return null
        }

        inline fun <reified T> build(callback: OkHttpCallback<T>?) {
            build(T::class.java, callback)
        }

        fun <T> build(clazz: Class<T>, callback: OkHttpCallback<T>?) {
            val builder = Request.Builder()
            builder.url(url)
            // if (AppConfig.isDebug()) Timber.d("build requestType = $requestType")
            when (requestType) {
                "POST" -> {
                    val call = getPostCall(builder)
                    enqueue(call, clazz, callback)
                }

                "GET" -> {
                    val call = getGetCall(builder)
                    enqueue(call, clazz, callback)
                }

                "PUT" -> {
                    val call = getPutCall(builder)
                    enqueue(call, clazz, callback)
                }

                "DELETE" -> {
                    val call = getDeleteCall(builder)
                    enqueue(call, clazz, callback)
                }
            }
        }

        private fun <T> enqueue(call: Call, clazz: Class<T>, callback: OkHttpCallback<T>?) {
            call.enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    if (AppConfig.isDebug()) Timber.e("onFailure:  ${e.message}, url = ${call.request().url.toUrl()}")
                    e.printStackTrace()
                    if (call.isCanceled() || isFinished(tag)) return
                    MainScope().launch {
                        if (isFinished(tag)) return@launch
                        if (e.message == "timeout") {
                            callback?.onError(401, "请求超时")
                        } else {
                            callback?.onFailure(e)
                        }
                    }
                }

                @Throws(IOException::class)
                override fun onResponse(call: Call, response: Response) {
                    if (call.isCanceled() || callback == null || isFinished(tag)) {
                        response.close()
                        return
                    }
                    handleResult(url, response, clazz, callback, avoidNullValue, tag)
                }
            })
        }

        private fun getPostCall(
            builder: Request.Builder,
            followRedirects: Boolean = true,
            addLogInterceptor: Boolean = AppConfig.isDebug()
        ): Call {
            for ((key, value) in commonHeader) {
                builder.addHeader(key, value.toString())
            }
            for ((key, value) in header) {
                builder.addHeader(key, value.toString())
            }
            var formBody: RequestBody? = null
            checkJsonBody()
            if (type == MediaTypeJSON) {
                val gson = if (jsonBody.isNotValid()) {
                    if (commonBody.size != 0)
                        body.putAll(commonBody)
                    GsonUtils.gson.toJson(body)
                } else {
                    jsonBody
                }
                // if (AppConfig.isDebug()) Timber.d("gson = $gson")
                formBody = gson.toRequestBody(type)
            } else if (type == MediaTypeDEFAULT) {
                val formBodyBuilder: FormBody.Builder = FormBody.Builder()
                for ((key, value) in commonBody) {
                    formBodyBuilder.add(key, value.toString())
                }
                for (entry in body.entries) {
                    formBodyBuilder.add(entry.key, entry.value.toString())
                }
                formBody = formBodyBuilder.build()
            }
            val request = builder.post(formBody!!).tag(tag).build()
            val clientBuilder = client.newBuilder()
                .proxy(if (AppConfig.allowNetProxy) null else Proxy.NO_PROXY) //直连，不走代理，防代理抓包
                .followRedirects(followRedirects)
            if (addLogInterceptor)
                clientBuilder.addNetworkInterceptor(HttpLogInterceptor())
            if (customReadTimeout != -1L) {
                clientBuilder.readTimeout(customReadTimeout, TimeUnit.MILLISECONDS)
            }
            if (customWriteTimeout != -1L) {
                clientBuilder.writeTimeout(customWriteTimeout, TimeUnit.MILLISECONDS)
            }
            val okHttpClient = clientBuilder.build()
            return okHttpClient.newCall(request)
        }

        private fun getGetCall(
            builder: Request.Builder,
            followRedirects: Boolean = true,
            addLogInterceptor: Boolean = AppConfig.isDebug()
        ): Call {
            //对url和参数做一下拼接处理
            val sb = StringBuffer()
            sb.append(url)
            if (url.contains("?")) {
                //如果？不在最后一位
                if (sb.indexOf("?") != sb.length - 1) {
                    sb.append("&")
                }
            } else {
                sb.append("?")
            }
            checkJsonBody()
            if (commonBody.size != 0)
                body.putAll(commonBody)
            for ((key, value) in body) {
                sb.append(key).append("=").append(value).append("&")
            }

            if (sb.indexOf("&") != -1) {
                sb.deleteCharAt(sb.lastIndexOf("&"))
            } else if (sb.endsWith("?")) {
                sb.deleteCharAt(sb.length - 1)
            }
            for ((key, value) in commonHeader) {
                builder.addHeader(key, value.toString())
            }
            for ((key, value) in header) {
                builder.addHeader(key, value.toString())
            }
            val request = builder.get().url(sb.toString()).tag(tag).build()
            val clientBuilder = client.newBuilder()
                .proxy(if (AppConfig.allowNetProxy) null else Proxy.NO_PROXY)
                .followRedirects(followRedirects)
            if (addLogInterceptor)
                clientBuilder.addNetworkInterceptor(HttpLogInterceptor())
            if (customReadTimeout != -1L) {
                clientBuilder.readTimeout(customReadTimeout, TimeUnit.MILLISECONDS)
            }
            if (customWriteTimeout != -1L) {
                clientBuilder.writeTimeout(customWriteTimeout, TimeUnit.MILLISECONDS)
            }
            val okHttpClient = clientBuilder.build()
            return okHttpClient.newCall(request)
        }

        private fun getPutCall(
            builder: Request.Builder,
            followRedirects: Boolean = true,
            addLogInterceptor: Boolean = AppConfig.isDebug()
        ): Call {
            for ((key, value) in commonHeader) {
                builder.addHeader(key, value.toString())
            }
            for ((key, value) in header) {
                builder.addHeader(key, value.toString())
            }
            var formBody: RequestBody? = null
            checkJsonBody()
            if (type == MediaTypeJSON) {
                val gson = if (jsonBody.isNotValid()) {
                    if (commonBody.size != 0)
                        body.putAll(commonBody)
                    GsonUtils.gson.toJson(body)
                } else {
                    jsonBody
                }
                if (AppConfig.isDebug()) Timber.d("gson = $gson")
                formBody = gson.toRequestBody(type)
            } else if (type == MediaTypeDEFAULT) {
                val formBodyBuilder: FormBody.Builder = FormBody.Builder()
                for ((key, value) in commonBody) {
                    formBodyBuilder.add(key, value.toString())
                }
                for (entry in body.entries) {
                    formBodyBuilder.add(entry.key, entry.value.toString())
                }
                formBody = formBodyBuilder.build()
            }
            val request = builder.put(formBody!!).tag(tag).build()
            val clientBuilder = client.newBuilder()
                .proxy(if (AppConfig.allowNetProxy) null else Proxy.NO_PROXY)
                .followRedirects(followRedirects)
            if (addLogInterceptor)
                clientBuilder.addNetworkInterceptor(HttpLogInterceptor())
            if (customReadTimeout != -1L) {
                clientBuilder.readTimeout(customReadTimeout, TimeUnit.MILLISECONDS)
            }
            if (customWriteTimeout != -1L) {
                clientBuilder.writeTimeout(customWriteTimeout, TimeUnit.MILLISECONDS)
            }
            val okHttpClient = clientBuilder.build()
            return okHttpClient.newCall(request)
        }

        private fun getDeleteCall(
            builder: Request.Builder,
            followRedirects: Boolean = true,
            addLogInterceptor: Boolean = true
        ): Call {
            for ((key, value) in commonHeader) {
                builder.addHeader(key, value.toString())
            }
            for ((key, value) in header) {
                builder.addHeader(key, value.toString())
            }
            var formBody: RequestBody? = null
            checkJsonBody()
            if (type == MediaTypeJSON) {
                val gson = if (jsonBody.isNotValid()) {
                    if (commonBody.size != 0)
                        body.putAll(commonBody)
                    GsonUtils.gson.toJson(body)
                } else {
                    jsonBody
                }
                if (AppConfig.isDebug()) Timber.d("gson = $gson")
                formBody = gson.toRequestBody(type)
            } else if (type == MediaTypeDEFAULT) {
                val formBodyBuilder: FormBody.Builder = FormBody.Builder()
                for ((key, value) in commonBody) {
                    formBodyBuilder.add(key, value.toString())
                }
                for (entry in body.entries) {
                    formBodyBuilder.add(entry.key, entry.value.toString())
                }
                formBody = formBodyBuilder.build()
            }
            val request = builder.delete(formBody!!).tag(tag).build()
            val clientBuilder = client.newBuilder()
                .proxy(if (AppConfig.allowNetProxy) null else Proxy.NO_PROXY)
                .followRedirects(followRedirects)
            if (addLogInterceptor)
                clientBuilder.addNetworkInterceptor(HttpLogInterceptor())
            if (customReadTimeout != -1L) {
                clientBuilder.readTimeout(customReadTimeout, TimeUnit.MILLISECONDS)
            }
            if (customWriteTimeout != -1L) {
                clientBuilder.writeTimeout(customWriteTimeout, TimeUnit.MILLISECONDS)
            }
            val okHttpClient = clientBuilder.build()
            return okHttpClient.newCall(request)
        }
    }
}